/*******************************************************************************
* Copyright (c) 2008 Innoopract Informationssysteme GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Innoopract Informationssysteme GmbH - initial API and implementation
******************************************************************************/
package org.eclipse.rap.ui.internal.preferences;
import java.util.*;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IPreferenceNodeVisitor;
import org.eclipse.osgi.util.NLS;
import org.eclipse.rwt.RWT;
import org.eclipse.rwt.internal.util.ParamCheck;
import org.eclipse.rwt.service.ISettingStore;
import org.eclipse.rwt.service.SettingStoreException;
import org.eclipse.ui.internal.preferences.Base64;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;
/**
* This node use the RWT setting store to persist its preferences.
*/
final class SessionPreferencesNode implements IEclipsePreferences {
private static final String PATH_SEPARATOR = "/"; //$NON-NLS-1$
private static final String DOUBLE_PATH_SEPARATOR = "//"; //$NON-NLS-1$
private static final String TRUE = "true"; //$NON-NLS-1$
private static final String FALSE = "false"; //$NON-NLS-1$
private final String name;
private final IEclipsePreferences parent;
private boolean isRemoved;
/* cache the absolutePath once it has been computed */
private String absolutePath;
private final Map children = new HashMap(); // !thread safe
SessionPreferencesNode( final IEclipsePreferences parent,
final String name )
{
ParamCheck.notNull( parent, "parent" ); //$NON-NLS-1$
ParamCheck.notNull( name, "name" ); //$NON-NLS-1$
checkName( name );
this.parent = parent;
this.name = name;
}
public void accept( final IPreferenceNodeVisitor visitor )
throws BackingStoreException
{
boolean withChildren = visitor.visit( this );
if( withChildren ) {
Object[] childrenArray;
synchronized( this ) {
childrenArray = children.values().toArray();
}
for( int i = 0; i < childrenArray.length; i++ ) {
IEclipsePreferences child = ( IEclipsePreferences )childrenArray[ i ];
child.accept( visitor );
}
}
}
public void addNodeChangeListener( final INodeChangeListener listener ) {
checkRemoved();
if( listener != null ) {
getNodeCore().addNodeChangeListener( listener );
}
}
public void addPreferenceChangeListener(
final IPreferenceChangeListener listener )
{
checkRemoved();
getNodeCore().addPreferenceChangeListener( listener );
}
public Preferences node( final String path ) {
checkPath( path );
checkRemoved();
Preferences result;
if( "".equals( path ) ) { // "" //$NON-NLS-1$
result = this;
} else if( path.startsWith( PATH_SEPARATOR ) ) { // "/absolute/path"
result = findRoot().node( path.substring( 1 ) );
} else if( path.indexOf( PATH_SEPARATOR ) > 0 ) { // "foo/bar/baz"
int index = path.indexOf( PATH_SEPARATOR );
String nodeName = path.substring( 0, index );
String rest = path.substring( index + 1, path.length() );
result = getChild( nodeName, true ).node( rest );
} else { // "foo"
result = getChild( path, true );
}
return result;
}
public synchronized void removeNode() throws BackingStoreException {
checkRemoved();
// remove all preferences
clear();
// remove all children
Object[] childNodes = children.values().toArray();
for( int i = 0; i < childNodes.length; i++ ) {
Preferences child = ( Preferences )childNodes[ i ];
if( child.nodeExists( "" ) ) { // if !removed //$NON-NLS-1$
child.removeNode();
}
}
// remove from parent; this is ugly, because the interface
// Preference has no API for removing oneself from the parent.
// In general the parent will be a SessionPreferencesNode.
// The only case in the workbench where this is not true, is one level
// below the root (i.e. at /session ), but the scope root must not
// be removable (see IEclipsePreferences#removeNode())
if( parent instanceof SessionPreferencesNode ) {
// this means:
// (a) we know what kind of parent we have, and
// (b) we are not the scope root, since that has a
/// RootPreference as a parent
SessionPreferencesNode spnParent
= ( ( SessionPreferencesNode ) parent );
spnParent.children.remove( name );
spnParent.fireNodeEvent( this, false );
// the listeners are not needed anymore
getNodeCore().clear();
children.clear();
isRemoved = true;
}
}
public void removeNodeChangeListener( final INodeChangeListener listener ) {
checkRemoved();
if( listener != null ) {
getNodeCore().removeNodeChangeListener( listener );
}
}
public void removePreferenceChangeListener(
final IPreferenceChangeListener listener )
{
checkRemoved();
getNodeCore().removePreferenceChangeListener( listener );
}
public String absolutePath() {
if( absolutePath == null ) {
if( parent == null ) {
absolutePath = name;
} else {
String parentPath = parent.absolutePath();
absolutePath = parentPath.endsWith( PATH_SEPARATOR )
? parentPath + name
: parentPath + PATH_SEPARATOR + name;
}
}
return absolutePath;
}
public synchronized String[] childrenNames() {
checkRemoved();
Set names = children.keySet();
return ( String[] )names.toArray( new String[ names.size() ] );
}
public void clear() {
checkRemoved();
String[] keys = internalGetKeys();
for( int i = 0; i < keys.length; i++ ) {
remove( keys[ i ] );
}
}
public void flush() {
checkRemoved();
// the current implementation persists everytime the preferences
// are modified, so there's nothing to do here
}
public String get( final String key, final String def ) {
ParamCheck.notNull( key, "key" ); //$NON-NLS-1$
checkRemoved();
String result = internalGet( key );
return result == null ? def : result;
}
public boolean getBoolean( final String key, final boolean def ) {
ParamCheck.notNull( key, "key" ); //$NON-NLS-1$
checkRemoved();
String value = internalGet( key );
return value == null ? def : Boolean.valueOf( value ).booleanValue();
}
public byte[] getByteArray( final String key, final byte[] def ) {
ParamCheck.notNull( key, "key" ); //$NON-NLS-1$
checkRemoved();
String value = internalGet( key );
return value == null ? def : Base64.decode( value.getBytes() );
}
public double getDouble( final String key, final double def ) {
ParamCheck.notNull( key, "key" ); //$NON-NLS-1$
checkRemoved();
String value = internalGet( key );
double result = def;
if( value != null ) {
try {
result = Double.parseDouble( value );
} catch( NumberFormatException nfe ) {
// returns def
}
}
return result;
}
public float getFloat( final String key, final float def ) {
ParamCheck.notNull( key, "key" ); //$NON-NLS-1$
checkRemoved();
String value = internalGet( key );
float result = def;
if( value != null ) {
try {
result = Float.parseFloat( value );
} catch( NumberFormatException nfe ) {
// returns def
}
}
return result;
}
public int getInt( final String key, final int def ) {
ParamCheck.notNull( key, "key" ); //$NON-NLS-1$
checkRemoved();
String value = internalGet( key );
int result = def;
if( value != null ) {
try {
result = Integer.parseInt( value );
} catch( NumberFormatException nfe ) {
// returns def
}
}
return result;
}
public long getLong( final String key, final long def ) {
ParamCheck.notNull( key, "key" ); //$NON-NLS-1$
checkRemoved();
String value = internalGet( key );
long result = def;
if( value != null ) {
try {
result = Long.parseLong( value );
} catch( NumberFormatException nfe ) {
// returns def
}
}
return result;
}
public String[] keys() {
checkRemoved();
return internalGetKeys();
}
public String name() {
return name;
}
public synchronized boolean nodeExists( final String path )
throws BackingStoreException
{
boolean result;
if( "".equals( path ) ) { //$NON-NLS-1$
result = !isRemoved;
} else {
checkRemoved();
checkPath( path );
if( path.startsWith( PATH_SEPARATOR ) ) { // "/absolute/path"
result = findRoot().nodeExists( path.substring( 1 ) );
} else if( path.indexOf( PATH_SEPARATOR ) > 0 ) { // "foo/bar/baz"
int index = path.indexOf( PATH_SEPARATOR );
String nodeName = path.substring( 0, index );
String rest = path.substring( index + 1, path.length() );
SessionPreferencesNode child = getChild( nodeName, false );
result = child == null ? false : child.nodeExists( rest );
} else { // "foo"
result = children.containsKey( path );
}
}
return result;
}
public Preferences parent() {
checkRemoved();
return parent;
}
public void put( final String key, final String newValue ) {
ParamCheck.notNull( key, "key" ); //$NON-NLS-1$
ParamCheck.notNull( newValue, "newValue" ); //$NON-NLS-1$
checkRemoved();
String oldValue = internalPut( key, newValue );
if( !newValue.equals( oldValue ) ) {
getNodeCore().firePreferenceEvent( key, oldValue, newValue );
}
}
public void putBoolean( final String key, final boolean value ) {
ParamCheck.notNull( key, "key" ); //$NON-NLS-1$
checkRemoved();
String newValue = value ? TRUE : FALSE;
String oldValue = internalPut( key, newValue );
if( !newValue.equals( oldValue ) ) {
getNodeCore().firePreferenceEvent( key, oldValue, newValue );
}
}
public void putByteArray( final String key, final byte[] value ) {
ParamCheck.notNull( key, "key" ); //$NON-NLS-1$
ParamCheck.notNull( value, "newValue" ); //$NON-NLS-1$
checkRemoved();
String newValue = new String( Base64.encode( value ) );
String oldValue = internalPut( key, newValue );
if( !newValue.equals( oldValue) ) {
getNodeCore().firePreferenceEvent( key, oldValue, newValue );
}
}
public void putDouble( final String key, final double value ) {
ParamCheck.notNull( key, "key" ); //$NON-NLS-1$
checkRemoved();
String newValue = String.valueOf( value );
String oldValue = internalPut( key, newValue );
if( !newValue.equals( oldValue ) ) {
getNodeCore().firePreferenceEvent( key, oldValue, newValue );
}
}
public void putFloat( final String key, final float value ) {
ParamCheck.notNull( key, "key" ); //$NON-NLS-1$
checkRemoved();
String newValue = String.valueOf( value );
String oldValue = internalPut( key, newValue );
if( !newValue.equals( oldValue ) ) {
getNodeCore().firePreferenceEvent( key, oldValue, newValue );
}
}
public void putInt( final String key, final int value ) {
ParamCheck.notNull( key, "key" ); //$NON-NLS-1$
checkRemoved();
String newValue = String.valueOf( value );
String oldValue = internalPut( key, newValue );
if( !newValue.equals( oldValue ) ) {
getNodeCore().firePreferenceEvent( key, oldValue, newValue );
}
}
public void putLong( final String key, final long value ) {
ParamCheck.notNull( key, "key" ); //$NON-NLS-1$
checkRemoved();
String newValue = String.valueOf( value );
String oldValue = internalPut( key, newValue );
if( !newValue.equals( oldValue ) ) {
getNodeCore().firePreferenceEvent( key, oldValue, newValue );
}
}
public void remove( final String key ) {
checkRemoved();
String oldValue = internalGet( key );
if( oldValue != null ) {
internalPut( key, null );
getNodeCore().firePreferenceEvent( key, oldValue, null );
}
}
public void sync() throws BackingStoreException {
checkRemoved();
ISettingStore store = RWT.getSettingStore();
String id = store.getId();
try {
store.loadById( id );
} catch( SettingStoreException sse ) {
throw new BackingStoreException( "Failed to sync() node", sse ); //$NON-NLS-1$
}
}
public String toString() {
return absolutePath() + "@" + hashCode(); //$NON-NLS-1$
}
//////////////////
// helping methods
private void checkName( final String nodeName ) {
if( nodeName.indexOf( PATH_SEPARATOR ) != -1 ) {
String unboundMsg = "Name ''{0}'' cannot contain or end with ''{1}''"; //$NON-NLS-1$
String msg = NLS.bind( unboundMsg, nodeName, PATH_SEPARATOR );
throw new IllegalArgumentException( msg );
}
}
private void checkPath( final String path ) {
if( path.indexOf( DOUBLE_PATH_SEPARATOR ) != -1 ) {
String unboundMsg = "''{0}'' is not allowed in path ''{1}''"; //$NON-NLS-1$
String msg = NLS.bind( unboundMsg, DOUBLE_PATH_SEPARATOR, path );
throw new IllegalArgumentException( msg );
}
if( path.length() > 1 && path.endsWith( PATH_SEPARATOR ) ) {
String unboundMsg = "path ''{0}'' cannot end with ''{1}''"; //$NON-NLS-1$
String msg = NLS.bind( unboundMsg, path, PATH_SEPARATOR );
throw new IllegalArgumentException( msg );
}
}
private synchronized void checkRemoved() {
if( isRemoved ) {
String msg = "node ''{0}'' has been removed"; //$NON-NLS-1$
throw new IllegalStateException( NLS.bind( msg, this.absolutePath() ) );
}
}
private synchronized SessionPreferencesNode createChild(
final String childName )
{
SessionPreferencesNode result
= new SessionPreferencesNode( this, childName );
children.put( childName, result );
fireNodeEvent( result, true );
return result;
}
private synchronized SessionPreferencesNode getChild(
final String childName,
final boolean doCreate )
{
SessionPreferencesNode result
= ( SessionPreferencesNode )children.get( childName );
if( result == null && doCreate ) {
result = createChild( childName );
}
return result;
}
private String[] internalGetKeys() {
List result = new ArrayList();
String prefix = absolutePath() + PATH_SEPARATOR;
int prefixLength = prefix.length();
Enumeration attrNames = RWT.getSettingStore().getAttributeNames();
while( attrNames.hasMoreElements() ) {
String attr = ( String )attrNames.nextElement();
if( attr.startsWith( prefix ) ) {
String key = attr.substring( prefixLength );
result.add( key );
}
}
return ( String[] )result.toArray( new String[ result.size() ] );
}
private Preferences findRoot() {
Preferences result = this;
while( result.parent() != null ) {
result = result.parent();
}
return result;
}
private String internalGet( final String key ) {
ISettingStore store = RWT.getSettingStore();
String uniqueKey = absolutePath() + PATH_SEPARATOR + key;
return store.getAttribute( uniqueKey );
}
private synchronized String internalPut( final String key,
final String value ) {
String uniqueKey = absolutePath() + PATH_SEPARATOR + key;
return getNodeCore().put( uniqueKey, value );
}
private void fireNodeEvent( final Preferences child,
final boolean wasAdded ) {
getNodeCore().fireNodeEvent( child, wasAdded, this );
}
private SessionPreferenceNodeCore getNodeCore() {
SessionPreferenceNodeCore result;
final String key = absolutePath();
Object object = RWT.getSessionStore().getAttribute( key );
if( object instanceof SessionPreferenceNodeCore ) {
result = ( SessionPreferenceNodeCore )object;
} else {
result = new SessionPreferenceNodeCore( this );
RWT.getSessionStore().setAttribute( key, result );
}
return result;
}
}